/*
    hdvb - Library for building sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Freesat Huffman decoding */

#include <glib.h>

#include <stdio.h>
#include <string.h>

#include "huffman.h"

#define START_TOKEN 0
#define STOP_TOKEN 0
#define ESC_TOKEN 1

extern HdvbHuffmanNode *freesat_huffman_table1[],
        *freesat_huffman_table2[];

inline static guint8 get_bit(const guint8 *input,
        int *input_bit, gsize *input_byte)
{
    guint8 token = (input[*input_byte] >> *input_bit) & 1;
    
    if (!(*input_bit)--)
    {
        *input_bit = 7;
        ++*input_byte;
    }
    return token;
}

inline static guint8 get_byte(const guint8 *input, gsize *input_byte,
        int input_bit, gsize input_len)
{
    guint8 token = (input[(*input_byte)++] & ((1 << (input_bit + 1)) - 1)) <<
            (7 - input_bit);
    
    if (input_bit != 7 && *input_byte < input_len)
    {
        token |= (input[*input_byte] >> (input_bit + 1)) &
                ((1 << (7 - input_bit)) - 1);
    }
    return token;
}

inline static char hex(guint8 nibble)
{
    return nibble < 10 ? nibble + '0' : nibble + 'A' - 10;
}

static char *fail(const char *reason, char *output, gsize output_len,
        const guint8 *input, gsize input_len)
{
    gsize n;
    gsize l = output_len + sizeof(H_HUFFMAN_FAIL_STRING) - 1 +
            strlen(reason) + 3;
    
    output = g_realloc(output, l + input_len * 2 + 1);
    sprintf(output, "%s%s%s --", output, H_HUFFMAN_FAIL_STRING, reason);
    for (n = 0; n < input_len; ++n)
    {
        output[n * 2 + l] = hex(input[n] >> 4);
        output[n * 2 + l + 1] = hex(input[n] & 15);
    }
    output[n * 2 + output_len] = 0;
    return output;
}

inline static void append_token(char **output, guint8 token,
        gsize *len, gsize *size)
{
    if (*len >= *size - 2)
    {
        *output = g_realloc(*output, *size *= 2);
    }
    (*output)[(*len)++] = (char) token;
    (*output)[*len] = 0;
}

char *hdvb_huffman_decode(const guint8 *input, gsize input_len,
        HdvbHuffmanNode **o1table)
{
    guint8 token;
    HdvbHuffmanNode *o0table;
    gsize input_byte = 0;
    int input_bit = 7;
    gsize output_size;
    gsize output_len = 0;
    unsigned char prev_char = START_TOKEN;
    char *output = g_malloc(output_size = input_len * 3);
    
    while (1)
    {
        char byte;
        
        while (prev_char < 128 && prev_char != ESC_TOKEN)
        {
            guint8 offset = 0;
            
            if ((o0table = o1table[prev_char]) == NULL)
            {
                char reason[64];
                
                sprintf(reason, "No O1 entry for prev_char %d (%c)",
                        prev_char, prev_char);
                return fail(reason, output, output_len, input, input_len);
            }
            do
            {
                if (input_byte >= input_len)
                {
                    return fail("Bit overrun",
                            output, output_len, input, input_len);
                }
                if (get_bit(input, &input_bit, &input_byte))
                    token = o0table[offset].right;
                else
                    token = o0table[offset].left;
                if (token & 128)
                {
                    if (token == (128 | STOP_TOKEN))
                        return output;
                    else if (token != (128 | ESC_TOKEN))
                    {
                        append_token(&output, token & 127,
                                &output_len, &output_size);
                    }
                    prev_char = token & 127;
                }
                else
                {
                    offset = token;
                }
            } while (!(token & 128));
        }
        byte = get_byte(input, &input_byte, input_bit, input_len);
        if (input_byte >= input_len && input_bit != 7)
        {
            return fail("Byte overrun", output, output_len, input, input_len);
        }
        append_token(&output, byte, &output_len, &output_size);
        prev_char = byte;
    }
    return NULL;
}
